/*
 * Copyright 2011 Angel Sanadinov
 *
 * This file is part of VBox WMI.
 *
 * VBox WMI is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * VBox WMI is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with VBox WMI.  If not, see <http://www.gnu.org/licenses/>.
 */

package Managers;

import Beans.ServerBean;
import com.sun.xml.ws.commons.virtualbox_3_2.ISession;
import com.sun.xml.ws.commons.virtualbox_3_2.IVirtualBox;
import java.util.HashMap;
import java.util.ArrayList;
import Managers.Utilities.VBoxConnection;
import Utilities.ConfigurationReader;
import Utilities.Constants;
import Utilities.Interfaces.Controllable;
import Utilities.Interfaces.SelfValidating;

/**
 * Manager for VirtualBox Web Service connections. <br><br>
 *
 * A <code>VBoxConnection</code> is created for each server in the database and
 * references to the <code>IVirtualBox</code> and <code>ISession</code> can be obtained
 * for each server.
 *
 * @see VBoxConnection
 *
 * @author Angel Sanadinov
 */
public class VBoxConnectionsManager implements SelfValidating, Controllable
{    
    private HashMap<Integer, VBoxConnection> connections;   //list of server connections
    private DatabaseManager databaseManager;                //reference to the database manager
    private LogsManager logsManager;                        //reference to the logs manager
    private int initialConnectionUsersListCapacity = 0; //initial user capacity of a VBox connection

    /**
     * Constructs the connections manager. <br><br>
     *
     * For each server retrieved from the database a <code>VBoxConnection</code>
     * object is created.
     *
     * @param database reference to the database manager
     * @param logger reference to the logs manager
     * @param configuration configuration parameters for the manager
     *
     * @see DatabaseManager Database Manager
     * @see LogsManager Logs Manager
     *
     * @see VBoxConnection
     */
    public VBoxConnectionsManager(DatabaseManager database, LogsManager logger, ConfigurationReader configuration)
    {
        this.databaseManager = database;
        this.logsManager = logger;

        try
        {
            initialConnectionUsersListCapacity = Integer.parseInt(
                    configuration.getParameter(ConfigurationReader.PARAM_VBCONNECTIONS_USERS_LIST_CAPACITY));
        }
        catch(NumberFormatException e)
        {
            this.logsManager.logSystemEvent(System.currentTimeMillis(), Constants.LOG_SEVERITY_WARNING,
                                            "Connections Manager: Failed to parse initialization parameter. "
                                            + "Default value (" + initialConnectionUsersListCapacity
                                            + ") for initialUsersListCapacity will be used.");
        }

        //retrieves the servers from the database and initializes the connections map
        ArrayList<ServerBean> servers = this.databaseManager.getServers();
        this.connections = new HashMap<Integer, VBoxConnection>(servers.size());

        //goes through each server and creates its VBoxConnection object
        for (ServerBean currentServer : servers)
        {
            //retrieves the credentials for the current server
            String[] serverCredentials = this.databaseManager.getServerCredentials(currentServer.getServerId());
            //builds the connection object and puts it in the map
            this.connections.put(currentServer.getServerId(),
                                 new VBoxConnection(currentServer.getWebServiceAddress(),
                                                    serverCredentials[0],
                                                    serverCredentials[1],
                                                    initialConnectionUsersListCapacity));
        }

        logsManager.logSystemEvent(System.currentTimeMillis(),
                                   Constants.LOG_SEVERITY_INFORMATION,
                                   "VirtualBox Connections Manager started...");
    }

    /**
     * Stops the manager and removes any associated resource references.
     */
    @Override
    public void stop()
    {
        logsManager.logSystemEvent(System.currentTimeMillis(),
                                   Constants.LOG_SEVERITY_INFORMATION,
                                   "VirtualBox Connections Manager stopping...");

        //terminates all server connections in the map
        for (VBoxConnection connection : connections.values())
            connection.logout();
        
        connections.clear();
        connections = null;
        databaseManager = null;
        logsManager = null;
    }

    /**
     * Retrieves a reference to the <code>IVirtualBox</code> object of the specified
     * server.
     *
     * @param serverId the id of the server
     * @param requestorId the id of the user that requested the operation
     * @return a reference to the <code>IVirtualBox</code> object of the server or
     *         <code>null</code> if such a reference could not be acquired
     *
     * @see VBoxConnection#getVirtualBoxReference(int) 
     */
    public IVirtualBox getVirtualBoxReference(int serverId, int requestorId)
    {
        if(connections.containsKey(serverId))
        {
            logsManager.logSystemEvent(System.currentTimeMillis(), Constants.LOG_SEVERITY_DEBUG, 
                                       "VirtualBox object reference retrieved for server ("
                                       + serverId + ") by user " + requestorId);
            return connections.get(serverId).getVirtualBoxReference(requestorId);
        }
        else
            return null;
    }

    /**
     * Retrieves a reference to the <code>ISession</code> object of the specified
     * server.
     *
     * @param serverId the id of the server
     * @param requestorId the id of the user that requested the operation
     * @return a reference to the <code>ISession</code> object of the server or
     *         <code>null</code> if such a reference could not be acquired
     *
     * @see VBoxConnection#getSessionReference(int) 
     */
    public ISession getSessionReference(int serverId, int requestorId)
    {
        if(connections.containsKey(serverId))
        {
            logsManager.logSystemEvent(System.currentTimeMillis(), Constants.LOG_SEVERITY_DEBUG,
                                       "Session object reference retrieved for server ("
                                       + serverId + ") by user " + requestorId);
            return connections.get(serverId).getSessionReference(requestorId);
        }
        else
            return null;
    }

    /**
     * Retrieves the number of servers that have been initialized. <br><br>
     *
     * <b>Note:</b> <i>A server is considered to be initialized after its <code>VBoxConnection</code>
     * object has been created.</i>
     *
     * @return the number of initialized servers
     */
    public int getNumberOfServers()
    {
        return connections.size();
    }

    /**
     * Retrieves the number of active connections to VirtualBox servers. <br><br>
     *
     * <b>Note:</b> <i>A connection to a server is established when a user requests
     * a reference to either the <code>IVirtualBox</code> or the <code>ISession</code>
     * object of the server. The connection is terminated when no users are using the
     * connections.</i>
     *
     * @return the number of established connections
     *
     * @see VBoxConnection
     */
    public int getNumberOfEstablishedConnections()
    {
        int result = 0;
        for (VBoxConnection connection : connections.values())
        {
            if(connection.isValid() && connection.isConnected())
                result++;
            else
                ;
        }

        return result;
    }

    /**
     * Updates the connection object of the specified server. <br><br>
     *
     * A connection object should be updated when a server is modified, created or removed.
     * When an update is triggered, the a new <code>VBoxConnection</code> is created,
     * the old connection is terminated and discarded.
     *
     * @param serverId the id of the server
     *
     * @see VBoxConnection
     */
    public void updateConnections(int serverId)
    {
        //checks if a valid server id is supplied
        if(serverId > Utilities.Constants.INVALID_SERVER_ID)
        {
            //retrieves the server data from the database
            ServerBean newServer = databaseManager.getServer(serverId);
            VBoxConnection oldConnection = null;

            logsManager.logSystemEvent(System.currentTimeMillis(), Constants.LOG_SEVERITY_DEBUG,
                                       "Performing connection update request for server: " + serverId);

            //checks if there is server data
            if(newServer != null)
            {
                //retrieves the credentials for the specified server
                String[] serverCredentials = databaseManager.getServerCredentials(serverId);

                //builds the new connection objects and puts it in the map
                oldConnection = connections.put(newServer.getServerId(),
                                                new VBoxConnection(newServer.getWebServiceAddress(),
                                                                   serverCredentials[0],
                                                                   serverCredentials[1],
                                                                   initialConnectionUsersListCapacity));
            }
            else //no data, the connection is to be removed
                oldConnection = connections.remove(serverId);

            //checks if there was a connection
            if(oldConnection != null)
                oldConnection.logout(); //terminates the connection
            else
                ;
        }
        else
            ;
    }

    /**
     * Unregisters the user with the specified id from all connections. <br><br>
     *
     * <b>Note:</b> <i>This method should only be called by the sessions manager!</i>
     *
     * @param userId the id of the user
     */
    public void unregisterConnectionUser(int userId)
    {
        if(userId > Utilities.Constants.INVALID_USER_ID)
        {
            for (VBoxConnection connection : connections.values())
                connection.unregisterConnectionUser(userId);
        }
        else
            ;
    }

    /**
     * Checks the validity of the manager. <br><br>
     * The manager is valid only if the references to the other required managers
     * are set and if the connections map has been initialized.
     *
     * @return <code>true</code> if the manager is valid
     */
    @Override
    public boolean isValid()
    {
        if(connections != null && databaseManager != null && logsManager != null)
            return true;
        else
            return false;
    }
}
